#include "Renderer.hpp"

#include <misc/aerScreenQuad.hpp>//
#include "Scene.hpp" //

#include <app/aerEventHandler.hpp>

using namespace aer;

Renderer::Renderer()
  : m_width(0),
    m_height(0)
{
}

Renderer::~Renderer()
{
  // ==============
  // == GBuffers ==
  // ==============
  m_gbuffer.diffuse.release();
  m_gbuffer.normal.release();
  m_gbuffer.motionDoF.release();
  m_gbuffer.depthStencil.release();
  m_gbuffer.framebuffer.release();
  
  // ======================
  // == Composition Pass ==
  // ======================
  m_compositionPass.program.release();
  m_compositionPass.color.release();
  m_compositionPass.framebuffer.release();
}


void Renderer::init(Data &data)
{
  // ===================
  // == OpenGL params ==
  // ===================
  glClearColor( 0.0f, 0.0f, 0.0f, 0.0f); //
  
  
  // ==============
  // == GBuffers ==
  // ==============
  m_gbuffer.diffuse.generate();
  m_gbuffer.normal.generate();
  m_gbuffer.motionDoF.generate();
  m_gbuffer.depthStencil.generate();
  
  m_gbuffer.diffuse.bind();
  m_gbuffer.diffuse.setFilters( Texture::NEAREST, Texture::NEAREST);
  m_gbuffer.diffuse.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
  
  m_gbuffer.normal.bind();
  m_gbuffer.normal.setFilters( Texture::NEAREST, Texture::NEAREST);
  m_gbuffer.normal.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
  
  m_gbuffer.motionDoF.bind();
  m_gbuffer.motionDoF.setFilters( Texture::NEAREST, Texture::NEAREST);
  m_gbuffer.motionDoF.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
  
  m_gbuffer.depthStencil.bind();
  m_gbuffer.depthStencil.setFilters( Texture::NEAREST, Texture::NEAREST);
  m_gbuffer.depthStencil.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
  
  Texture::Unbind( Texture::TEXTURE_2D );
  
  m_gbuffer.framebuffer.generate();

  
  // ======================
  // == Composition Pass ==
  // ======================
  m_compositionPass.program.generate();
  {
    Program &program = m_compositionPass.program;
    program.addShader( Program::VERTEX_SHADER,   "PostProcess.Vertex");
    program.addShader( Program::FRAGMENT_SHADER, "LightComposition.Fragment");
  }
  AER_CHECK( m_compositionPass.program.link() );

  
  m_compositionPass.color.generate();
  m_compositionPass.color.bind();
    m_compositionPass.color.setFilters( Texture::NEAREST, Texture::NEAREST);
    m_compositionPass.color.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
  m_compositionPass.color.unbind();
  
  m_compositionPass.framebuffer.generate();
  
  
  // ==============
  // == Lighting ==
  // ==============
  m_lighting.init();
  
  
  // =================
  // == PostProcess ==
  // =================
  
  m_effects.dof.setFocusPlaneDistance( 100.0f );//
  m_effects.dof.setFocusRange( 10.0f );//
  
  m_effects.bloom.setBloomFactor( 0.50f );
  m_effects.bloom.setThreshold( 0.10f );
  
  //m_postProcess.addEffect( &m_effects.bloom );
  //m_postProcess.addEffect( &m_effects.ssao );
  m_postProcess.addEffect( &m_effects.dof );
  m_postProcess.addEffect( &m_effects.passthrough );
  m_postProcess.init();
  
  
  m_postProcess.linkInput( "Color", &m_compositionPass.color);
  m_postProcess.linkInput( "Depth", &m_gbuffer.depthStencil);
  m_postProcess.linkInput( "LinearParams", &data.effectsParams.linearizationParams);
  //m_postProcess.linkInput( "Timer", &data.timer);  
  m_postProcess.link();
 
}


void Renderer::resize( const int width, const int height)
{
  AER_ASSERT( width > 0 && height > 0 );
  
  m_width = width;
  m_height = height;
  
  
  // ==============
  // == GBuffers ==
  // ==============
  /// [RGB Diffuse + A Specular]
  m_gbuffer.diffuse.bind();
  m_gbuffer.diffuse.resize( m_width, m_height, GL_RGBA8);
  
  /// [RGB XYZ-normal] 
  m_gbuffer.normal.bind();
  m_gbuffer.normal.resize( m_width, m_height, GL_RGBA16F);
  
  /// [RG XY-motion + B DoF] (too big ?)
  m_gbuffer.motionDoF.bind();
  m_gbuffer.motionDoF.resize( m_width, m_height, GL_RGBA16F);
  
  /// [Depth24 + Stencil8]
  m_gbuffer.depthStencil.bind();
  m_gbuffer.depthStencil.resize( m_width, m_height, GL_DEPTH24_STENCIL8);
  
  Texture::Unbind( Texture::TEXTURE_2D );
  
  // ??
  /// Not sure if we needed to reattach it every time;
  /// think not, but for now it's easier for initialization
  /// because buffer (texture) must be setup first..
  m_gbuffer.framebuffer.bind();
  {
    FrameBuffer &fbo = m_gbuffer.framebuffer;
    
    fbo.attachColorBuffer( &m_gbuffer.diffuse,   GL_COLOR_ATTACHMENT0);
    fbo.attachColorBuffer( &m_gbuffer.normal,    GL_COLOR_ATTACHMENT1);
    fbo.attachColorBuffer( &m_gbuffer.motionDoF, GL_COLOR_ATTACHMENT2);
    fbo.attachSpecialBuffer( &m_gbuffer.depthStencil, GL_DEPTH_STENCIL_ATTACHMENT);
    
    AER_CHECK( FrameBuffer::CheckStatus() );
  }
  m_gbuffer.framebuffer.unbind();
  m_gbuffer.framebuffer.resize( m_width, m_height); //
    
  
  // ======================
  // == Composition Pass ==
  // ======================
  /// Final composition texture
  m_compositionPass.color.bind();
    m_compositionPass.color.resize( m_width, m_height, GL_RGBA8);
  m_compositionPass.color.unbind();
  
  /// [??] Same remark as previously
  m_compositionPass.framebuffer.bind();
  {
    FrameBuffer &fbo = m_compositionPass.framebuffer;
    
    fbo.attachColorBuffer( &m_compositionPass.color,  GL_COLOR_ATTACHMENT0);
    fbo.attachSpecialBuffer( &m_gbuffer.depthStencil, GL_DEPTH_STENCIL_ATTACHMENT);
    
    AER_CHECK( FrameBuffer::CheckStatus() );
  }
  m_compositionPass.framebuffer.unbind();
  m_compositionPass.framebuffer.resize( m_width, m_height);//
  
  
  // ==============
  // == Lighting ==
  // ==============
  m_lighting.resize( width, height);  
  
  
  // =================
  // == PostProcess ==
  // =================
  m_postProcess.resize( width, height);
  
  
  CHECKGLERROR();
}


void Renderer::update(Data &data)
{
  float fpd = m_effects.dof.getFocusPlaneDistance();
  float fr = m_effects.dof.getFocusRange();
  
  if (aer::EventHandler::IsKeyDown( sf::Keyboard::A )) { m_effects.dof.setFocusPlaneDistance(fpd-1.0f); }
  if (aer::EventHandler::IsKeyDown( sf::Keyboard::E )) { m_effects.dof.setFocusPlaneDistance(fpd+1.0f); }
  
  if (aer::EventHandler::IsKeyDown( sf::Keyboard::R )) { m_effects.dof.setFocusRange(fr-1.0f); }
  if (aer::EventHandler::IsKeyDown( sf::Keyboard::T )) { m_effects.dof.setFocusRange(fr+1.0f); }
  
}


void Renderer::run(Data &data)
{  
  /// 1 - PreProcess
  //
    
  /// 2 - Fill GBuffers
  m_gbuffer.framebuffer.begin();
    data.pScene->render( Scene::OPAQUE | Scene::DECALS, data);
  m_gbuffer.framebuffer.end();

  /// 3 - Deferred light accumulation
  m_lighting.applyShadowmap();//
  m_lighting.applyAccumulation( data, &m_gbuffer.normal, &m_gbuffer.depthStencil);//
  //previously  "m_lighting.apply( Lighting::SHADOWMAP | Lighting::ACCUMULATION );"
  
  /// 4 - Deferred & forward light composition
  m_compositionPass.framebuffer.begin();
    compositionPass();
    data.pScene->render( Scene::TRANSPARENT, data);
  m_compositionPass.framebuffer.end();
  
  /// 5 - PostProcess
  m_postProcess.run();
  //postProcess(); //tmp
  
  
  
  /// [Optional] Debug pass
  //
  
  CHECKGLERROR();
}


void Renderer::compositionPass()
{  
  m_gbuffer.diffuse.bind(0);
  m_lighting.accumulationPass.color.bind(1);
  
  m_compositionPass.program.bind();
  {
    m_compositionPass.program.setUniform( "uDiffuseTex",  0);
    m_compositionPass.program.setUniform( "uLightingTex", 1);
    
    ScreenQuad::Draw();
  }
  m_compositionPass.program.unbind();
  
  m_gbuffer.diffuse.unbind();
  m_lighting.accumulationPass.color.unbind();
  
  // To finish in the default texture unit
  Texture::Activate( 0 );
}



// ---- temporary to debug -----
void Renderer::postProcess()
{
  static Program program;
  static unsigned int frame = 0u;
  
  if (!program.isGenerated())
  {
    program.generate();
      program.addShader( Program::VERTEX_SHADER,   "PostProcess.Vertex");
      program.addShader( Program::FRAGMENT_SHADER, "PostProcess.Fragment");
    AER_CHECK( program.link() );
  }
  
  //----------
  
  glClear( GL_DEPTH_BUFFER_BIT );
    
  m_compositionPass.color.bind();
  
  program.bind();
    program.setUniform( "uSceneTex", 0);
    //program.setUniform( "uEnableVignetting", true);
    //program.setUniform( "uEnableGammaCorrection", true);
    
    program.setUniform( "uRand", rand() / float(RAND_MAX));
    program.setUniform( "uTime", (++frame)/60.0f);
    program.setUniform( "uTexelSize", 1.0f / Vector2(m_width, m_height));
    
    ScreenQuad::Draw();
  program.unbind();
  
  m_compositionPass.color.unbind();
}
